#ifdef F_CPU
#undef F_CPU
#endif

//the internal oscillator
#define F_CPU (8000000UL)

#include <avr/io.h>

FUSES = 
    {
        0xE2,
        0xDF,
        0xF9
    };

#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/eeprom.h>
#include <avr/fuse.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <util/delay.h>
#include <util/atomic.h>
#include "util.h"
#include "animations.h"

/*
 * 	The row sinks and the column sources current
 *	so the row has to have row bits high but the one that we need to turn on
 *	and the column has to have all column bits low but the one we need to turn on
 */

#define ROW_PORT	PORTB
#define COL_PORT	PORTD
#define ROW_PIXEL(x)	(ROW_PORT = 0b11111111 ^ _BV(x))
#define ROW_OFF()		(ROW_PORT = 0b11111111)
#define COL_PIXEL(y)	(COL_PORT = 0b00000000 ^ _BV(y))
#define CLEAR() \
do { \
	ROW_PORT = 0xFF; \
	COL_PORT = 0xFF; \
} while( 0 )

#if defined(_AVR_IOM8_H_) || defined(__AVR_ATmega8__)
	#define STATUS_REGISTER		TWDR
#elif defined(_AVR_IOM328P_H_) || defined(_AVR_IOM48_H_) || defined(_AVR_IOM88_H_) || defined(__AVR_ATmega88PA__)
	#define STATUS_REGISTER		GPIOR0
#endif

typedef struct {
	uint8_t frameTick : 1;
	uint8_t advanceScanPixel : 1;
	uint8_t buttonShortPress : 1;
	uint8_t buttonLongPress : 1;
	uint8_t wokeFromSleep : 1;
} tSystemState;

#define systemState (*((volatile tSystemState*)&STATUS_REGISTER))

static void initDevice( void );
static void sleepDevice( void );

uint8_t batteryLevel;
uint16_t measureBattery( void );
uint8_t batteryLevelFromMV( uint16_t MV );

//frame timing
//~500 interrupts per second
#define TIMER2_COMPARE_VALUE	125

static void initTimer2( void );
void startTimer2( void );
void stopTimer2( void );

//scan pixel timing
//~4K interrupts per second
#define TIMER0_TOP_VALUE	32

static void initTimer0( void );
void startTimer0( void );
void stopTimer0( void );

void setPixel( uint8_t x, uint8_t y );
void clearPixel( uint8_t x, uint8_t y );
void drawBar( uint8_t size, uint8_t row );

int main(void)
{
	uint8_t currentScanPixel = 0;
	uint8_t modVal, divVal;
	static uint8_t rowByte;

	uint8_t buttonDetectCount = 0;

	uint16_t batteryMV;

	cli();
	initDevice();

	initTimer2();
	initTimer0();
	
	CLEAR_BUFFER( FRAME() );
	CLEAR_BUFFER( BUFFER() );

	animation.currentAnimationIndex = 0;
	ANIMATION animationFunction = animationList[ animation.currentAnimationIndex ];

	batteryMV = measureBattery();
	batteryLevel = batteryLevelFromMV( batteryMV );
	setAnimationFunction( anim_batteryLevel, TRUE, TRUE, FALSE );

	sei();

	startTimer2();
	startTimer0();

    while ( 1 )
    {
    	if( systemState.advanceScanPixel == TRUE )
    	{
			currentScanPixel++;
			currentScanPixel &= NUMBER_OF_LEDS_IDX;

			modVal = currentScanPixel & 0b111; // modulo 8
			if( modVal == 0 )
			{
				divVal = currentScanPixel >> 3; // division by 8
				//we need to change columns only so often
				COL_PORT = _BV( divVal );
				rowByte = FRAME()[ divVal ];
			}

			ROW_PORT = ~( rowByte & _BV( modVal ) );
			systemState.advanceScanPixel = FALSE;
    	}

    	if( systemState.frameTick == TRUE )
    	{
			animationTick();
			if( animation.active == FALSE && animation.currentAnimation == anim_batteryLevel )
			{
				ANIMATION animationFunction = animationList[ animation.currentAnimationIndex ];
				setAnimationFunction( animationFunction, TRUE, TRUE, TRUE );
			}

			//button is pressed
			if( pinc.pin1 == OFF )
			{
				buttonDetectCount++;
				if( buttonDetectCount > 200 )
				{
					systemState.buttonLongPress = TRUE;
					buttonDetectCount = 0;
				}
			}
			//button is depressed
			else
			{
				//debounce
				if( buttonDetectCount > 20 )
				{
					if( systemState.wokeFromSleep == TRUE )
					{
						systemState.wokeFromSleep = FALSE;	
					}
					else
					{
						systemState.buttonShortPress = TRUE;
					}
				}
				buttonDetectCount = 0;					
			}

    		systemState.frameTick = FALSE;	
    	}
		if( ( animation.nextFrameReady == TRUE ) && ( currentScanPixel == 0 ) )
		{
			animationBufferSwap();
			animation.nextFrameReady = FALSE;
		}

		//this can happen only if the system is running and not asleep
		if( systemState.buttonShortPress == TRUE )
		{
			// srand( animation.ticksForCurrentFrame );
			// animation.currentAnimationIndex = ((uint8_t)rand()) & 0b111;
			animation.currentAnimationIndex++;
			animation.currentAnimationIndex &= 0b111;
			animationFunction = animationList[ animation.currentAnimationIndex ];
			setAnimationFunction( animationFunction, TRUE, TRUE, TRUE );

			systemState.buttonShortPress = FALSE;
		}
		//we sleep here
		else if( systemState.buttonLongPress == TRUE )
		{
			stopTimer0();
			stopTimer2();

			//turn off all pixels
			CLEAR();
			//wait, in hope that the user will release the button
			//otherwise, if the user holds on to the button, this will trigger a wake up event
			_delay_ms( 1000 );
			sleepDevice();

			batteryMV = measureBattery();
			batteryLevel = batteryLevelFromMV( batteryMV );
			setAnimationFunction( anim_batteryLevel, TRUE, TRUE, FALSE );

			startTimer0();
			startTimer2();

			systemState.wokeFromSleep = TRUE;
			systemState.buttonLongPress = FALSE;
		}

    }
}

void initDevice( void )
{
	//POWER SAVINGS
	//setup and disable ADC
	//measure internal 1.1volt relative to AVcc
	ADMUX = _BV( REFS0 ) | _BV( MUX3 ) | _BV( MUX2 ) | _BV( MUX1 );
	ADCSRA = _BV( ADPS0 ) | _BV( ADPS1 ) | _BV( ADPS2 );
	ADCSRA &= ~_BV( ADEN );
	//now wrtite to PRR to disable TWI, SPI, USART
	PRR |= _BV( PRTWI ) | _BV( PRTIM1 ) | _BV( PRSPI ) | _BV( PRUSART0 ) | _BV( PRADC );
	//disable watchdog
	WDTCSR &= ~( _BV( WDE ) | _BV( WDIE ) );

	//PORT INITIALIZATION
	//init ports
	DDRB 	= 0b11111111;
	DDRD 	= 0b11111111;
	//set almost the entire port as output 
	DDRC 	= 0b11111001;
	PORTC 	= 0b01000110;

	//outright disable the digital input buffer on the battery pin PIN2 on PORTC
	DIDR0	|= _BV( ADC2D ); 

	//enable button reading
	ddrc.dd1 = IN;
	portc.port1 = PULLUP_ENABLED;

	//init Timer0
	//set timer in CTC mode OCR0A as top
	TCCR0A |= _BV( WGM01 );
	//set top
	OCR0A = TIMER0_TOP_VALUE;
	//enable compare match interrupt
	TIMSK0	|= _BV( OCIE0A );

	//init Timer2
	//CTC mode
	TCCR2A	|= _BV( WGM21 );
	//set top; about 500 FPS
	OCR2A	= TIMER2_COMPARE_VALUE;
	//enable compare match interrupt
	TIMSK2	|= _BV( OCIE2A );

	//BUTTON SETUP
	//set pin change interrupt on our button
	PCMSK1	|= _BV( PCINT9 );
}

//we need the interrupt just to wake from deep sleep
EMPTY_INTERRUPT( PCINT1_vect );

void sleepDevice( void )
{
	//init sleep mode
	set_sleep_mode( SLEEP_MODE_PWR_DOWN );

	//enable pin change interrupt, but first clear any pin change flag set
	PCIFR 	|= _BV( PCIF1 );
	PCICR 	|= _BV( PCIE1 );

	cli();
	sleep_enable();
	sei();
	sleep_cpu();

	//system continues execution here when the button is pressed
	sleep_disable();

	//disable pin change interrupt
	PCICR	&= ~_BV( PCIE1 );
}

/**
 * https://code.google.com/p/tinkerit/wiki/SecretVoltmeter
 */
uint16_t measureBattery( void )
{
	//enable ADC in PRR
	PRR &= ~_BV( PRADC );
	ADCSRA |= _BV( ADEN );
	ADCSRA |= _BV( ADSC );
	//discard first conversion
	while( ADCSRA & _BV( ADSC ) );
	uint8_t counter = 8;
	uint16_t adcValue = 0;
	while( counter )
	{
		ADCSRA |= _BV( ADSC );
		while( ADCSRA & _BV( ADSC ) );
		adcValue += ADC;
		counter--;
	}
	adcValue >>= 3;
	//disable ADC again
	ADCSRA &= ~_BV( ADEN );
	PRR |= _BV( PRADC );
	adcValue = 1126400L / adcValue;
	return adcValue;
}

uint8_t batteryLevelFromMV( uint16_t MV )
{
	if( MV < 3100 )
	{
		return 0;
	}
	else if ( MV >= 3100 && MV < 3300 )
	{
		return 1;
	}
	else if ( MV >= 3300 && MV < 3500 )
	{
		return 2;
	}
	else if ( MV >= 3500 && MV < 3600 )
	{
		return 3;
	}
	else if ( MV >= 3600 && MV < 3700 )
	{
		return 4;
	}
	else if ( MV >= 3700 && MV < 3800 )
	{
		return 5;
	}
	else if ( MV >= 3800 && MV < 3900 )
	{
		return 6;
	}
	else if ( MV >= 3900 )
	{
		return 7;
	}
	return 0;
}


void setPixel( uint8_t x, uint8_t y )
{
	COL_PIXEL( y );
	ROW_PIXEL( x );
}

void clearPixel( uint8_t x, uint8_t y )
{
	COL_PIXEL( y );
	ROW_OFF();
}

void drawBar( uint8_t size, uint8_t row )
{
	uint8_t cnt;
	for( cnt = 0; cnt < size; cnt++ )
	{
		setPixel( cnt, row );
	}
}


void initTimer0( void )
{
	//set timer in CTC mode OCR0A as top
	TCCR0A |= _BV( WGM01 );
	//set top
	OCR0A = TIMER0_TOP_VALUE;
	//enable compare match interrupt
	TIMSK0	|= _BV( OCIE0A );
}

void startTimer0( void )
{
	//set prescaler to 64
	TCNT0 = 0;
	TCCR0B |=  PRESCALE_64;
}

void stopTimer0( void )
{
	//set prescaler to 0
	TCCR0B &=  ~( PRESCALE_64 );
}

ISR( TIMER0_COMPA_vect, ISR_NAKED )
{
	systemState.advanceScanPixel = TRUE;
	asm volatile ("reti");
}

void initTimer2( void )
{
	//CTC mode
	TCCR2A	|= _BV( WGM21 );
	//set top; about 500 FPS
	OCR2A	= TIMER2_COMPARE_VALUE;
	//enable compare match interrupt
	TIMSK2	|= _BV( OCIE2A );
}

void startTimer2( void )
{
	//set prescaler to 128
	TCCR2B	|=  ( E_PRESCALE_128 );
}

void stopTimer2( void )
{
	TCCR2B	&=  ~( E_PRESCALE_STOP );
}

ISR( TIMER2_COMPA_vect, ISR_NAKED )
{
	systemState.frameTick = TRUE;
	asm volatile ("reti");
}